package com.gitee.wsl.ext.string

import com.gitee.wsl.ext.ApplicationConfig.lineSeparator

fun CharSequence.charAt(index: Int) = this[index]

fun CharSequence.isEmptyOrSpaces( ): Boolean {
    for (c in this) {
        if (c != ' ' && c != '\t') {
            return false
        }
    }
    return true
}

fun CharSequence.indexOfNextToken(
    startIndex: Int = 0,
): Pair<Int, TokenType> {
    val (index, matched) = findAnyOf(
        strings = TokenType.allPossibleTokens,
        startIndex = startIndex,
        ignoreCase = true
    ) ?: return -1 to TokenType.NONE
    val tokenType = when (matched) {
        in TokenType.ARRAY.delimiters -> TokenType.ARRAY
        in TokenType.OBJECT.delimiters -> TokenType.OBJECT
        in TokenType.KEY_SEPARATOR.delimiters -> TokenType.KEY_SEPARATOR
        in TokenType.VALUE_SEPARATOR.delimiters -> TokenType.VALUE_SEPARATOR
        in TokenType.STRING.delimiters -> TokenType.STRING
        in TokenType.BOOLEAN.delimiters -> TokenType.BOOLEAN
        else -> null
    }
    tokenType?.let {
        return index to it
    }
    return -1 to TokenType.NONE
}

fun CharSequence.indexOfNextUnescapedQuote(startIndex: Int = 0): Int {
    var index = indexOf('"', startIndex)
    while (index < length) {
        if (this[index] == '"' && (index == 0 || this[index - 1] != '\\')) {
            return index
        }
        index = indexOf('"', index + 1)
    }
    return -1
}

/**
 * Indents each line of [this] string by [indent].
 * @param indent indentation string
 * @return [this] string, indented
 */
fun CharSequence.indent(indent: String) =
    buildString {
        this@indent.lineSequence()
            .filterNot { it.isEmpty() }
            .forEach { append(indent).append(it).append("\n") }
    }

/**
 * @return [this] string with line separators replaced with `\n`,
 *         or the string itself if `\n` is already the line separator
 */
fun CharSequence.normalizeLineSeparators(): CharSequence =
    when (val separator = lineSeparator()) {
        "\n" -> this
        else -> this.toString().replace(separator, "\n")
    }

fun CharSequence.indicesOf(input: String) = input.toRegex(RegexOption.IGNORE_CASE).findAll(this).map { it.range.first }
    /*Pattern.compile(input, Pattern.CASE_INSENSITIVE).toRegex()
        .findAll(this)
        .map { it.range.first }
        .toCollection(mutableListOf())*/


fun CharSequence.findNextNewLineChar(start:Int):Int{
    for (i in start until length) {
        if (this[i]== '\n') {
            return i
        }
    }
    return -1
}

fun CharSequence.findBeforeNewLineChar(start:Int):Int{
    for (i in start - 1 downTo 1) {
        if (this[i] == '\n') {
            return i
        }
    }
    return -1
}



/** Remove all consecutive line breaks in a `CharSequence` */
fun CharSequence.removeDuplicateEmptyLines(): CharSequence =
    lines().removeDuplicateEmptyLines()

/** Add an empty line as last item if it is not empty */
fun CharSequence.endWithNewLine(): CharSequence =
    if (lines().lastOrNull().isNullOrBlank()) this else "$this\n"


fun CharSequence.isLetter(index: Int) = this[index].isLetter()

fun CharSequence.isSpaceOrTab(index: Int): Boolean {
    if (index < length) {
        when (this[index]) {
            ' ', '\t' -> return true
        }
    }
    return false
}

fun CharSequence.hasNonSpace(): Boolean {
    val skipped: Int = skip(' ', 0, length)
    return skipped != length
}

fun CharSequence.findLineBreak( startIndex: Int): Int {
    for (i in startIndex until length) {
        when (this[i]) {
            '\n', '\r' -> return i
        }
    }
    return -1
}

fun CharSequence.find(c: Char,  startIndex: Int): Int {
    for (i in startIndex until length) {
        if (this[i] == c) {
            return i
        }
    }
    return -1
}

fun CharSequence.skip(skip: Char, startIndex: Int, endIndex: Int): Int {
    for (i in startIndex until endIndex) {
        if (this[i] != skip) {
            return i
        }
    }
    return endIndex
}


fun CharSequence.skipBackwards(skip: Char,  startIndex: Int, lastIndex: Int): Int {
    for (i in startIndex downTo lastIndex) {
        if (this[i] != skip) {
            return i
        }
    }
    return lastIndex - 1
}

fun CharSequence.skipSpaceTab( startIndex: Int, endIndex: Int): Int {
    for (i in startIndex until endIndex) {
        when (this[i]) {
            ' ', '\t' -> {}
            else -> return i
        }
    }
    return endIndex
}

fun CharSequence.skipSpaceTabBackwards( startIndex: Int, lastIndex: Int): Int {
    for (i in startIndex downTo lastIndex) {
        when (this[i]) {
            ' ', '\t' -> {}
            else -> return i
        }
    }
    return lastIndex - 1
}

fun CharSequence.isHorizontalLine(offset: Int): Boolean {
    val line = this
    var hrChar: Char? = null
    var startSpace = 0
    var charCount = 1
    for (i in offset..< line.length) {
        val c = line[i]
        if (hrChar == null) {
            if (c == '*' || c == '-' || c == '_') {
                hrChar = c
            } else if (startSpace < 3 && c == ' ') {
                startSpace++
            } else {
                return false
            }
        } else {
            if (c == hrChar) {
                charCount++
            } else if (c != ' ' && c != '\t') {
                return false
            }
        }
    }
    return charCount >= 3
}


fun CharSequence.indexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false) =
    this.indexOf(char, startIndex, ignoreCase).takeIf { it != -1 }

fun CharSequence.indexOfOrNull(string: String, startIndex: Int = 0, ignoreCase: Boolean = false) =
    this.indexOf(string, startIndex, ignoreCase).takeIf { it != -1 }

fun CharSequence.lastIndexOfOrNull(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false) =
    this.lastIndexOf(char, startIndex, ignoreCase).takeIf { it != -1 }

fun CharSequence.lastIndexOfOrNull(string: String, startIndex: Int = lastIndex, ignoreCase: Boolean = false) =
    this.lastIndexOf(string, startIndex, ignoreCase).takeIf { it != -1 }


// get -----------------------------------------------------------------------------------------------------------------

/**
 * Returns the character at the specified [range] in this character sequence.
 *
 * @throws [IllegalArgumentException] if the [IntRange.first] isn't equal to [IntRange.last].
 * @throws [IndexOutOfBoundsException] if the [range] is out of bounds of this character sequence.
 */
 operator fun CharSequence.get(range: IntRange): kotlin.Char {
    require(range.first == range.last) { "The requested range $range is not suitable to get a single character." }
    checkBoundsIndexes(length, range.first, range.last + 1)
    return this[range.first]
}



// checkBoundsIndex ----------------------------------------------------------------------------------------------------

/**
 * Returns the specified [index] if it in the specified [range].
 * Otherwise, throws an [IndexOutOfBoundsException].
 */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkBoundsIndex(range: IntRange, index: Int): Int {
    if (range.contains(index)) return index
    throw IndexOutOfBoundsException("index out of range: $index")
}

// checkBoundsIndexes --------------------------------------------------------------------------------------------------

/**
 * Returns a new range from [startIndex] until [endIndex] if:
 * 1) [startIndex] is less than [endIndex],
 * 2) [startIndex] is greater than or equal to `0`, and
 * 3) [endIndex] is less than the specified [length].
 *
 * Otherwise, throws an [IndexOutOfBoundsException].
 */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkBoundsIndexes(length: Int, startIndex: Int, endIndex: Int): IntRange {
    if (startIndex in 0..endIndex && endIndex <= length) return startIndex until endIndex
    throw IndexOutOfBoundsException("begin $startIndex, end $endIndex, length $length")
}

/**
 * Returns the specified [range] if:
 * 1) it's not empty,
 * 2) [IntRange.first] is greater than or equal to `0`, and
 * 3) [IntRange.last] is less than the specified [length].
 *
 * Otherwise, throws an [IndexOutOfBoundsException].
 */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkBoundsIndexes(length: Int, range: IntRange): IntRange =
    checkBoundsIndexes(length, range.first, range.last + 1)


// requireNotEmpty -----------------------------------------------------------------------------------------------------

/** Throws an [IllegalArgumentException] if the specified [charSequence] [isEmpty]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun requireNotEmpty(charSequence: CharSequence): CharSequence = charSequence.also { require(it.isNotEmpty()) }

/** Throws an [IllegalArgumentException] if the specified [string] [isEmpty]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun requireNotEmpty(string: String): String = string.also { require(it.isNotEmpty()) }

/** Throws an [IllegalArgumentException] with the result of calling [lazyMessage] if the specified [charSequence] [isEmpty]. */
 inline fun requireNotEmpty(charSequence: CharSequence, lazyMessage: () -> Any): CharSequence =
    charSequence.also { require(it.isNotEmpty(), lazyMessage) }

/** Throws an [IllegalArgumentException] with the result of calling [lazyMessage] if the specified [string] [isEmpty]. */
 inline fun requireNotEmpty(string: String, lazyMessage: () -> Any): String = string.also { require(it.isNotEmpty(), lazyMessage) }


// requireNotBlank -----------------------------------------------------------------------------------------------------

/** Throws an [IllegalArgumentException] if the specified [charSequence] [isBlank]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun requireNotBlank(charSequence: CharSequence): CharSequence = charSequence.also { require(charSequence.isNotBlank()) }

/** Throws an [IllegalArgumentException] if the specified [string] [isBlank]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun requireNotBlank(string: String): String = string.also { require(string.isNotBlank()) }

/** Throws an [IllegalArgumentException] with the result of calling [lazyMessage] if the specified [charSequence] [isBlank]. */
 inline fun requireNotBlank(charSequence: CharSequence, lazyMessage: () -> Any): CharSequence =
    charSequence.also { require(it.isNotBlank(), lazyMessage) }

/** Throws an [IllegalArgumentException] with the result of calling [lazyMessage] if the specified [string] [isBlank]. */
 inline fun requireNotBlank(string: String, lazyMessage: () -> Any): String = string.also { require(it.isNotBlank(), lazyMessage) }


// checkNotEmpty -------------------------------------------------------------------------------------------------------
/** Throws an [IllegalStateException] if the specified [charSequence] [isEmpty]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkNotEmpty(charSequence: CharSequence): CharSequence = charSequence.also { check(it.isNotEmpty()) }

/** Throws an [IllegalStateException] if the specified [string] [isEmpty]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkNotEmpty(string: String): String = string.also { check(it.isNotEmpty()) }

/** Throws an [IllegalStateException] with the result of calling [lazyMessage] if the specified [charSequence] [isEmpty]. */
 inline fun checkNotEmpty(charSequence: CharSequence, lazyMessage: () -> Any): CharSequence = charSequence.also { check(it.isNotEmpty(), lazyMessage) }

/** Throws an [IllegalStateException] with the result of calling [lazyMessage] if the specified [string] [isEmpty]. */
 inline fun checkNotEmpty(string: String, lazyMessage: () -> Any): String = string.also { check(it.isNotEmpty(), lazyMessage) }


// checkNotBlank -------------------------------------------------------------------------------------------------------

/** Throws an [IllegalStateException] if the specified [charSequence] [isBlank]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkNotBlank(charSequence: CharSequence): CharSequence = charSequence.also { check(it.isNotBlank()) }

/** Throws an [IllegalStateException] if the specified [string] [isBlank]. */
@Suppress("NOTHING_TO_INLINE")
 inline fun checkNotBlank(string: String): String = string.also { check(it.isNotBlank()) }

/** Throws an [IllegalStateException] with the result of calling [lazyMessage] if the specified [charSequence] [isBlank]. */
 inline fun checkNotBlank(charSequence: CharSequence, lazyMessage: () -> Any): CharSequence = charSequence.also { check(it.isNotBlank(), lazyMessage) }

/** Throws an [IllegalStateException] with the result of calling [lazyMessage] if the specified [string] [isBlank]. */
 inline fun checkNotBlank(string: String, lazyMessage: () -> Any): String = string.also { check(it.isNotBlank(), lazyMessage) }


// spaced / startSpaced / endSpaced ------------------------------------------------------------------------------------

/** Returns this char with a space added to each side if not already present, or an empty string otherwise. */
 inline val Char?.spaced: String get() = startSpaced.endSpaced

/** Returns this char with a space added to the beginning if not already present, or an empty string otherwise. */
 inline val Char?.startSpaced: String get() = this?.run { withPrefix(" ") } ?: String.EMPTY

/** Returns this char with a space added to the end if not already present, or an empty string otherwise. */
 inline val Char?.endSpaced: String get() = this?.run { withSuffix(" ") } ?: String.EMPTY

/** Returns this character sequence with a space added to each side if not already present, or an empty string otherwise. */
 inline val CharSequence?.spaced: CharSequence get() = startSpaced.endSpaced

/** Returns this character sequence with a space added to the beginning if not already present, or an empty string otherwise. */
 inline val CharSequence?.startSpaced: CharSequence get() = if (this == null) String.EMPTY else if (isBlank()) this else withPrefix(" ")

/** Returns this character sequence with a space added to the end if not already present, or an empty string otherwise. */
 inline val CharSequence?.endSpaced: CharSequence get() = if (this == null) String.EMPTY else if (isBlank()) this else withSuffix(" ")

/** Returns this string with a space added to each side if not already present, or an empty string otherwise. */
 inline val String?.spaced: String get() = startSpaced.endSpaced

/** Returns this string with a space added to the beginning if not already present, or an empty string otherwise. */
 inline val String?.startSpaced: String get() = if (this == null) String.EMPTY else if (isBlank()) this else withPrefix(" ")

/** Returns this string with a space added to the end if not already present, or an empty string otherwise. */
 inline val String?.endSpaced: String get() = if (this == null) String.EMPTY else if (isBlank()) this else withSuffix(" ")


// truncate ------------------------------------------------------------------------------------------------------------

/**
 * Returns this character sequence truncated from the center to up to 15 code points
 * including the [marker] (default: " … ").
 */
// fun CharSequence.truncate(marker: CharSequence = Unicode.ELLIPSIS.spaced): CharSequence = truncate(15.codePoints, marker)

/**
 * Returns this string truncated from the center to up to 15 code points
 * including the [marker] (default: " … ").
 */
// fun String.truncate(marker: CharSequence = Unicode.ELLIPSIS.spaced): String = truncate(15.codePoints, marker)


/**
 * Returns this character sequence truncated from the center to the specified [length] (default: 15)
 * including the [marker] (default: " … ").
 */
// fun CharSequence.truncate(
//    length: TextLength<*>,
//    marker: CharSequence = Unicode.ELLIPSIS.spaced,
//): CharSequence = mapText(length.unit) {
//    it.truncate(length.value, marker.asText(length.unit))
//}

/**
 * Returns this string truncated from the center to the specified [length] (default: 15)
 * including the [marker] (default: " … ").
 */
// fun String.truncate(
//    length: TextLength<*>,
//    marker: CharSequence = Unicode.ELLIPSIS.spaced,
//): String = mapText(length.unit) { it.truncate(length.value, marker.asText(length.unit)) }

/**
 * Returns this character sequence truncated from the start to up to 15 code points
 * including the [marker] (default: "… ").
 */
// fun CharSequence.truncateStart(marker: CharSequence = Unicode.ELLIPSIS.endSpaced): CharSequence =
//    truncateStart(15.codePoints, marker)

/**
 * Returns this string truncated from the start to up to 15 code points
 * including the [marker] (default: "… ").
 */
// fun String.truncateStart(marker: CharSequence = Unicode.ELLIPSIS.endSpaced): String =
//    truncateStart(15.codePoints, marker)

/**
 * Returns this character sequence truncated from the start to the specified [length] (default: 15)
 * including the [marker] (default: "… ").
 */
// fun CharSequence.truncateStart(
//    length: TextLength<*>,
//    marker: CharSequence = Unicode.ELLIPSIS.endSpaced,
//): CharSequence = mapText(length.unit) { it.truncateStart(length.value, marker.asText(length.unit)) }

/**
 * Returns this string truncated from the start to the specified [length] (default: 15)
 * including the [marker] (default: "… ").
 */
// fun String.truncateStart(
//    length: TextLength<*>,
//    marker: CharSequence = Unicode.ELLIPSIS.endSpaced,
//): String = mapText(length.unit) { it.truncateStart(length.value, marker.asText(length.unit)) }

/**
 * Returns this character sequence truncated from the end to up to 15 code points
 * including the [marker] (default: " …").
 */
// fun CharSequence.truncateEnd(marker: CharSequence = Unicode.ELLIPSIS.startSpaced): CharSequence =
//    truncateEnd(15.codePoints, marker)

/**
 * Returns this string truncated from the end to up to 15 code points
 * including the [marker] (default: " …").
 */
// fun String.truncateEnd(marker: CharSequence = Unicode.ELLIPSIS.startSpaced): CharSequence =
//    truncateEnd(15.codePoints, marker)

/**
 * Returns this character sequence truncated from the end to the specified [length] (default: 15)
 * including the [marker] (default: " …").
 */
// fun CharSequence.truncateEnd(
//    length: TextLength<*>,
//    marker: CharSequence = Unicode.ELLIPSIS.startSpaced,
//): CharSequence = mapText(length.unit) { it.truncateEnd(length.value, marker.asText(length.unit)) }

/**
 * Returns this string truncated from the end to the specified [length] (default: 15)
 * including the [marker] (default: " …").
 */
// fun String.truncateEnd(
//    length: TextLength<*>,
//    marker: CharSequence = Unicode.ELLIPSIS.startSpaced,
//): String = mapText(length.unit) { it.truncateEnd(length.value, marker.asText(length.unit)) }

// repeat --------------------------------------------------------------------------------------------------------------

/** Returns a string containing this char repeated [n] times. */
 fun Char.repeat(n: Int): String = toString().repeat(n)


// indexOfOrNull -------------------------------------------------------------------------------------------------------

/**
 * Returns the index within this string of the first occurrence of the specified character,
 * starting from the specified [startIndex].
 *
 * @param ignoreCase `true` to ignore the character case when matching a character. By default `false`.
 * @return An index of the first occurrence of [char] or `null` if none is found.
 */
// fun CharSequence.indexOfOrNull(char: kotlin.Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int? =
//    indexOf(char, startIndex, ignoreCase).takeIf { it >= 0 }

/**
 * Returns the index within this character sequence of the first occurrence of the specified [string],
 * starting from the specified [startIndex].
 *
 * @param ignoreCase `true` to ignore the character case when matching a string. By default `false`.
 * @return An index of the first occurrence of [string] or `null` if none is found.
 */
// fun CharSequence.indexOfOrNull(string: String, startIndex: Int = 0, ignoreCase: Boolean = false): Int? =
//    indexOf(string, startIndex, ignoreCase).takeIf { it >= 0 }


// lastIndexOfOrNull ---------------------------------------------------------------------------------------------------

/**
 * Returns the index within this character sequence of the last occurrence of the specified character,
 * starting from the specified [startIndex].
 *
 * @param startIndex The index of character to start searching at. The search proceeds backward toward the beginning of the string.
 * @param ignoreCase `true` to ignore the character case when matching a character. By default `false`.
 * @return An index of the last occurrence of [char] or `null` if none is found.
 */
// fun CharSequence.lastIndexOfOrNull(char: kotlin.Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int? =
//    lastIndexOf(char, startIndex, ignoreCase).takeIf { it >= 0 }

/**
 * Returns the index within this character sequence of the last occurrence of the specified [string],
 * starting from the specified [startIndex].
 *
 * @param startIndex The index of character to start searching at. The search proceeds backward toward the beginning of the string.
 * @param ignoreCase `true` to ignore the character case when matching a string. By default `false`.
 * @return An index of the last occurrence of [string] or `null` if none is found.
 */
// fun CharSequence.lastIndexOfOrNull(string: String, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int? =
//    lastIndexOf(string, startIndex, ignoreCase).takeIf { it >= 0 }



internal class DelimitedRangesSequence(
    private val input: CharSequence,
    private val startIndex: Int,
    private val limit: Int,
    private val getNextMatch: CharSequence.(currentIndex: Int) -> Pair<Int, Int>?,
) : Sequence<IntRange> {

    override fun iterator(): Iterator<IntRange> = object : Iterator<IntRange> {
        var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue
        var currentStartIndex: Int = startIndex.coerceIn(0, input.length)
        var nextSearchIndex: Int = currentStartIndex
        var nextItem: IntRange? = null
        var counter: Int = 0

        private fun calcNext() {
            if (nextSearchIndex < 0) {
                nextState = 0
                nextItem = null
            } else {
                if (limit > 0 && ++counter >= limit || nextSearchIndex > input.length) {
                    nextItem = currentStartIndex..input.lastIndex
                    nextSearchIndex = -1
                } else {
                    val match = input.getNextMatch(nextSearchIndex)
                    if (match == null) {
                        nextItem = currentStartIndex..input.lastIndex
                        nextSearchIndex = -1
                    } else {
                        val (index, length) = match
                        nextItem = currentStartIndex until index
                        currentStartIndex = index + length
                        nextSearchIndex = currentStartIndex + if (length == 0) 1 else 0
                    }
                }
                nextState = 1
            }
        }

        override fun next(): IntRange {
            if (nextState == -1)
                calcNext()
            if (nextState == 0)
                throw NoSuchElementException()
            val result = nextItem as IntRange
            // Clean next to avoid keeping reference on yielded instance
            nextItem = null
            nextState = -1
            return result
        }

        override fun hasNext(): Boolean {
            if (nextState == -1)
                calcNext()
            return nextState == 1
        }
    }
}

/**
 * Matches a character if it is not escaped.
 * @param string the string to match
 * @param position the position of the character to match
 * @param char the character to match
 * @param onMatch optional action to perform if the character is matched
 * @return 1 if the character is matched and not preceded by an escape character, 0 otherwise
 */
fun CharSequence.unescapedMatch(
    position: Int,
    char: Char,
    onMatch: () -> Unit = {},
): Int =
    when {
        this[position] != char -> 0
        getOrNull(position - 1) != '\\' -> {
            onMatch()
            1
        }
        else -> 0
    }

fun CharSequence.matchUnescaped(
    position: Int,
    startChar: Char,
    endChar: Char = startChar,
    onMatch: CharSequence.( start : Int, end: Int, depth:Int ) -> Unit
):Int{
    var inArg = false
    val subPositionMap = mutableMapOf<Int,Int>()
    unescapedMatch(position, startChar) {
        inArg = true
        subPositionMap.put(0,position)
    }
    if(!inArg) return 0

    var depth = 0

    for (x in position until length) {
        when {
            // Unescaped argument begin character {
            unescapedMatch(x, startChar) != 0 -> {
                depth++
                subPositionMap.put(depth, x)
            }
            // Unescaped argument end character }
            // This leads to the end of the argument if the delimiters are balanced.
            unescapedMatch(x, endChar) != 0 -> {
                val subStart = subPositionMap[depth]!!
                onMatch(subStart,x,depth)
                if(depth == 0) return x - position
                subPositionMap.remove(depth)
                depth--
            }
        }
    }
    return 0
}